package com.zbs.framework.common.utils;

import com.zbs.framework.common.utils.date.DateRange;
import com.zbs.framework.common.utils.date.TimeTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
public class LocalDateUtil {
    /**
     * 英文简写（默认）如：2010-12-01
     */
    public static final String FORMAT_SHORT = "yyyy-MM-dd";
    /**
     * 英文简写（默认）如：2010-12-01
     */
    public static final String FORMAT_DIY = "yyyy/MM/dd";
    /**
     * 英文全称  如：2010-12-01 23:15:06
     */
    public static final String FORMAT_LONG = "yyyy-MM-dd HH:mm:ss";
    /**
     * 精确到毫秒的完整时间    如：yyyy-MM-dd HH:mm:ss.S
     */
    public static final String FORMAT_FULL = "yyyy-MM-dd HH:mm:ss.SSS";
    /**
     * 中文简写  如：2010年12月01日
     */
    public static final String FORMAT_SHORT_CN = "yyyy年MM月dd";
    /**
     * 中文全称  如：2010年12月01日  23时15分06秒
     */
    public static final String FORMAT_LONG_CN = "yyyy年MM月dd日  HH时mm分ss秒";

    /**
     * 精确到毫秒的完整中文时间
     */
    public static final String FORMAT_FULL_CN = "yyyy年MM月dd日  HH时mm分ss秒SSS毫秒";

    public static String format(LocalDateTime dateTime) {
        return format(dateTime, "");
    }

    public static String getStartTimeStr(LocalDateTime dateTime) {
        String startTime = null;
        if (dateTime != null) {
            startTime = LocalDateUtil.format(LocalDateUtil.getStartTime(dateTime));
        }
        return startTime;
    }

    public static String getEndTimeStr(LocalDateTime dateTime) {
        String endTime = null;
        if (dateTime != null) {
            endTime = LocalDateUtil.format(LocalDateUtil.getEndTime(dateTime));
        }
        return endTime;
    }

    public static LocalDateTime timestampToDatetime(long timestamp) {
        Instant instant = Instant.ofEpochSecond(timestamp);
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }

    public static String format(LocalDateTime dateTime, String pattern) {
        return dateTime.format(DateTimeFormatter.ofPattern(StringUtils.hasLength(pattern) ? pattern : FORMAT_LONG));
    }

    public static LocalDateTime parse() {
        return parse(LocalDateTime.now(), "");
    }

    public static LocalDateTime parse(String pattern) {
        return parse(LocalDateTime.now(), pattern);
    }

    public static LocalDateTime parse(LocalDateTime localDateTime) {
        return parse(localDateTime, "");
    }

    public static LocalDateTime getStartTime(String dateTime) {
        return getStartTime(Objects.requireNonNull(strToLocalDateTime(dateTime)));
    }

    public static LocalDateTime getEndTime(String dateTime) {
        return getEndTime(Objects.requireNonNull(strToLocalDateTime(dateTime)));
    }

    public static LocalDateTime getStartTime(Date date) {
        return getStartTime(toLocalDateTime(date));
    }

    public static LocalDateTime getEndTime(Date date) {
        return getEndTime(toLocalDateTime(date));
    }

    public static LocalDateTime getEveryMonthFirstDay(LocalDateTime dateTime) {
        return LocalDateTime.of(LocalDate.from(dateTime.with(TemporalAdjusters.firstDayOfMonth())), LocalTime.MIN);
    }

    public static LocalDateTime getEveryMonthLastDay(LocalDateTime dateTime) {
        return parse(LocalDateTime.of(LocalDate.from(dateTime.with(TemporalAdjusters.lastDayOfMonth())), LocalTime.MAX));
    }

    public static LocalDateTime getEveryYearFirstDay(LocalDateTime dateTime) {
        return LocalDateTime.of(LocalDate.from(dateTime.with(TemporalAdjusters.firstDayOfYear())), LocalTime.MIN);
    }

    public static LocalDateTime getEveryYearLastDay(LocalDateTime dateTime) {
        return parse(LocalDateTime.of(LocalDate.from(dateTime.with(TemporalAdjusters.lastDayOfYear())), LocalTime.MAX));
    }

    public static LocalDateTime getEveryYearFirstDay(Date date) {
        return getEveryYearFirstDay(toLocalDateTime(date));
    }

    public static LocalDateTime getEveryYearLastDay(Date date) {
        return getEveryYearLastDay(toLocalDateTime(date));
    }

    public static LocalDateTime getEveryMonthFirstDay(Date date) {
        return getEveryMonthFirstDay(toLocalDateTime(date));
    }

    public static LocalDateTime getEveryMonthLastDay(Date date) {
        return getEveryMonthLastDay(toLocalDateTime(date));
    }


    public static LocalDateTime getStartTime(LocalDateTime dateTime) {
        return LocalDateTime.of(dateTime.toLocalDate(), LocalTime.MIN);
    }

    public static LocalDateTime getEndTime(LocalDateTime dateTime) {
        return parse(LocalDateTime.of(dateTime.toLocalDate(), LocalTime.MAX));
    }

    public static LocalDateTime firstDayOfMonth(LocalDateTime dateTime) {
        return dateTime.with(TemporalAdjusters.firstDayOfMonth());
    }

    public static LocalDateTime addDay(Integer num) {
        return LocalDateTime.now().plusDays(num);
    }

    public static LocalDateTime addDay(LocalDateTime date, Integer num) {
        return date.plusDays(num);
    }

    public static LocalDateTime addDay(Long timestamp, Integer num) {
        return timestampToLocalDateTime(timestamp).plusDays(num);
    }

    public static LocalDateTime parse(LocalDateTime dateTime, String pattern) {
        return strToLocalDateTime(dateTime.format(DateTimeFormatter.ofPattern(StringUtils.hasLength(pattern) ? pattern : FORMAT_LONG)));
    }

    public static LocalDateTime toLocalDateTime(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    public static Date toDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * LocalDateTime 转 时间戳(秒级别)
     *
     * @param localDateTime
     * @return Long
     * @author Caixiaowei
     * @updateTime 2020/9/8 10:17
     */
    public static Long localDateTimeToSecond(LocalDateTime localDateTime) {
        return localDateTime.toEpochSecond(ZoneOffset.ofHours(8));
    }

    public static Long localDateTimeToSecond(LocalDateTime localDateTime,Integer timeZone) {
        return localDateTime.toEpochSecond(ZoneOffset.ofHours(timeZone));
    }

    /**
     * LocalDateTime 转 时间戳(毫秒级别)
     *
     * @param localDateTime
     * @return Long
     * @author Caixiaowei
     * @updateTime 2020/9/8 10:17
     */
    public static Long localDateTimeToMilliseconds(LocalDateTime localDateTime) {
        return localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
    }

    public static Long localDateTimeToMilliseconds(LocalDateTime localDateTime,Integer timeZone) {
        return localDateTime.toInstant(ZoneOffset.ofHours(timeZone)).toEpochMilli();
    }


    public static LocalDateTime strToLocalDateTime(String time) {
        DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern(FORMAT_SHORT);
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern(FORMAT_LONG);
        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        try {
            if (StringUtils.hasLength(time) && !time.equals("null")) {
                if (time.contains("T")) {
                    return LocalDateTime.parse(time, formatter);
                }
                if (time.length() == 10) {
                    return LocalDateTime.of(LocalDate.parse(time, formatter1), LocalTime.MIN);
                } else {
                    return LocalDateTime.parse(time, formatter2);
                }
            }
        } catch (Exception e) {
            log.error("时间格式错误:", e);
        }
        return null;
    }

    /**
     * 计算时间差
     *
     * @param beginTime 开始时间
     * @param endTime   结束时间
     * @return Duration
     */
    public static Duration between(LocalTime beginTime, LocalTime endTime) {
        return Duration.between(beginTime, endTime);
    }

    /**
     * 计算时间差
     *
     * @param beginTime 开始时间
     * @param endTime   结束时间
     * @return Duration
     */
    public static Duration between(LocalDateTime beginTime, LocalDateTime endTime) {
        return Duration.between(beginTime, endTime);
    }

    /**
     * 计算时间差
     *
     * @param beginTime 开始时间
     * @param endTime   结束时间
     * @return Duration
     */
    public static Duration between(LocalDate beginTime, LocalDate endTime) {
        return Duration.between(beginTime, endTime);
    }

    /**
     * 当前时间是否在时间指定范围内<br>
     *
     * @param time      被检查的时间
     * @param beginTime 起始时间
     * @param endTime   结束时间
     * @return 是否在范围内
     * @since 3.0.8
     */
    public static boolean isIn(LocalTime time, LocalTime beginTime, LocalTime endTime) {
        return beginTime.isBefore(time) && endTime.isAfter(time);
    }


    /**
     * 当前时间是否在时间指定范围内<br>
     *
     * @param time      被检查的时间
     * @param beginTime 起始时间
     * @param endTime   结束时间
     * @return 是否在范围内
     * @since 3.0.8
     */
    public static boolean isIn(LocalDateTime time, LocalDateTime beginTime, LocalDateTime endTime) {
        return beginTime.isBefore(time) && endTime.isAfter(time);
    }


    /**
     * 当前时间是否在时间指定范围内<br>
     *
     * @param time      被检查的时间
     * @param beginTime 起始时间
     * @return 是否在范围内
     * @since 3.0.8
     */
    public static boolean isBefore(LocalTime time, LocalTime beginTime) {
        return beginTime.isBefore(time);
    }

    /**
     * 当前时间是否在时间指定范围内<br>
     *
     * @param time      被检查的时间
     * @param beginTime 起始时间
     * @return 是否在范围内
     * @since 3.0.8
     */
    public static boolean isBefore(LocalDateTime time, LocalDateTime beginTime) {
        return beginTime.isBefore(time);
    }

    /**
     * 当前时间是否在时间指定范围内<br>
     *
     * @param time    被检查的时间
     * @param endTime 结束时间
     * @return 是否在范围内
     * @since 3.0.8
     */
    public static boolean isAfter(LocalTime time, LocalTime endTime) {
        return endTime.isAfter(time);
    }


    /**
     * 当前时间是否在时间指定范围内<br>
     *
     * @param time    被检查的时间
     * @param endTime 结束时间
     * @return 是否在范围内
     * @since 3.0.8
     */
    public static boolean isAfter(LocalDateTime time, LocalDateTime endTime) {
        return endTime.isAfter(time);
    }


    public static LocalDateTime timestampToLocalDateTime(Long timestamp) {
        if (timestamp == null) {
            return null;
        }
        return Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
    }

    public static LocalDate timestampToLocalDate(Long timestamp) {
        if (timestamp == null) {
            return null;
        }
        return Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDate();
    }

    /**
     * 根据起始时间、结束时间、时间类型和间隔参数，返回时间区间列表
     * 间隔时间为1
     *
     * @param startTime 起始时间
     * @param endTime   结束时间
     * @param timeType  时间类型，包括年、月和日
     * @return 包含多个时间区间的列表
     */
    public static List<DateRange> getIntervalTimeList(LocalDateTime startTime, LocalDateTime endTime, TimeTypeEnum timeType) {
        return getIntervalTimeList(startTime, endTime, timeType, 1L);
    }

    /**
     * 根据起始时间、结束时间、时间类型和间隔参数，返回时间区间列表
     *
     * @param startTime   起始时间
     * @param endTime     结束时间
     * @param timeType    时间类型，包括年、月和日
     * @param intervalNum 间隔参数，表示时间区间的间隔数
     * @return 包含多个时间区间的列表
     */
    public static List<DateRange> getIntervalTimeList(LocalDateTime startTime, LocalDateTime endTime, TimeTypeEnum timeType, Long intervalNum) {
        if (intervalNum == null || intervalNum < 1) {
            intervalNum = 1L;
        }

        List<DateRange> intervalTimeList = new ArrayList<>();
        LocalDateTime currentStartTime = startTime;
        LocalDateTime currentEndTime;

        while (currentStartTime.isBefore(endTime)) {
            switch (timeType) {
                case YEAR:
                    currentEndTime = currentStartTime.plusYears(intervalNum).minusSeconds(1);
                    break;
                case MONTH:
                    currentEndTime = currentStartTime.plusMonths(intervalNum).minusSeconds(1);
                    break;
                case DAYS: {
                    LocalDate currentDate = currentStartTime.toLocalDate();
                    LocalDate nextDate = currentDate.plusDays(intervalNum);
                    currentEndTime = nextDate.atTime(0, 0, 0).minusSeconds(1);
                    if (currentEndTime.isAfter(endTime)) {
                        currentEndTime = endTime;
                    }
                }
                break;
                case HOUR:
                    currentEndTime = currentStartTime.plusHours(intervalNum).minusSeconds(1);
                    break;
                case MINUTES:
                    currentEndTime = currentStartTime.plusMinutes(intervalNum).minusSeconds(1);
                    break;
                case SECONDS:
                    currentEndTime = currentStartTime.plusSeconds(intervalNum).minusSeconds(1);
                    break;
                default:
                    throw new IllegalArgumentException("Invalid time type: " + timeType);
            }

            if (currentEndTime.isAfter(endTime)) {
                currentEndTime = endTime;
            }

            intervalTimeList.add(new DateRange(currentStartTime, currentEndTime));
            currentStartTime = currentEndTime.plusSeconds(1);
        }

        return intervalTimeList;
    }

    public static List<LocalDate> getBetweenLocalDate(LocalDate startDate, LocalDate endDate, Long intervalNum) {
        LocalDateTime startTime = LocalDateTime.of(startDate, LocalTime.of(0, 0, 0));
        LocalDateTime endTime = LocalDateTime.of(endDate, LocalTime.of(23, 59, 59));
        return getBetweenLocalDateTime(startTime,endTime,TimeTypeEnum.DAYS,intervalNum)
                .stream()
                .map(LocalDateTime::toLocalDate)
                .collect(Collectors.toList());
    }

    public static List<LocalDateTime> getBetweenLocalDateTime(LocalDateTime startTime, LocalDateTime endTime, TimeTypeEnum timeTypeEnum, Long intervalNum) {
        List<LocalDateTime> result = new ArrayList<>();
        LocalDateTime temp = startTime;
        while (temp.isBefore(endTime)) {
            result.add(temp);
            switch (timeTypeEnum) {
                case YEAR:
                    temp = temp.plusYears(intervalNum);
                    break;
                case MONTH:
                    temp = temp.plusMonths(intervalNum);
                    break;
                case DAYS:
                    temp = temp.plusDays(intervalNum);
                    break;
                case HOUR:
                    temp = temp.plusHours(intervalNum);
                    break;
                case MINUTES:
                    temp = temp.plusMinutes(intervalNum);
                    break;
                case SECONDS:
                    temp = temp.plusSeconds(intervalNum);
                    break;
            }
        }
        return result;
    }
}
